import datetime
import os
import time

import requests
from loguru import logger

requests.packages.urllib3.disable_warnings()


class gupiao:
    def __init__(self):
        self.url = "https://flash-api.xuangubao.com.cn/api/pool/detail"
        self.headers = {
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0"
        }
        self.proxies = {
            'http': 'socks5://127.0.0.1:8442',
            'https': 'socks5://127.0.0.1:8442'
        }
        self.today_par = {"pool_name": "limit_up"}
        self.data_dir = "data"  # 存放数据的目录

        # 确保数据目录存在
        os.makedirs(self.data_dir, exist_ok=True)

    def get_today_data(self, save_data=True):
        response = self.retry_request(
            requests.get,
            self.url,
            headers=self.headers,
            params=self.today_par,
            verify=False,
            # proxies=self.proxies,
        )
        if save_data:
            today_filename = os.path.join(
                self.data_dir,
                f"today_data_{datetime.datetime.now().strftime('%Y%m%d')}.json",
            )
            with open(today_filename, "w", encoding="utf-8") as f:
                f.write(response.text)
                logger.info(f"保存今日数据成功: {today_filename}")
        return response.json()

    def retry_request(self, request_func, *args, **kwargs):
        """Attempt to make an API request with retries."""
        retries = 10  # Number of retry attempts
        delay = 2  # Delay in seconds between attempts
        for attempt in range(retries):
            try:
                response = request_func(*args, **kwargs)
                response.raise_for_status()  # Raise an error for bad responses
                return response
            except requests.RequestException as e:
                logger.error(f"请求失败: {e}, 尝试重试 {attempt + 1}/{retries}")
                time.sleep(delay)  # Wait before retrying
        logger.error("请求失败, 已达最大重试次数")
        raise
    def get_history_data(self, date="2025-07-15", save_data=True):
        history_par = {"pool_name": "limit_up", "date": date}
        response = self.retry_request(
            requests.get,
            self.url,
            headers=self.headers,
            params=history_par,
            verify=False,
            # proxies=self.proxies,
        )
        if save_data:
            history_filename = os.path.join(
                self.data_dir, f"history_data_{date.replace('-', '')}.json"
            )
            with open(history_filename, "w", encoding="utf-8") as f:
                f.write(response.text)
                logger.info(f"保存历史数据成功: {history_filename}")
        return response.json()

    def generate_plate_index(self):
        # 加载当天数据
        today_data = self.get_today_data()
        data = today_data.get("data", [])

        plate_count = {}

        for item in data:
            surge_reason = item.get("surge_reason", {})
            if surge_reason is None:
                continue

            related_plates = surge_reason.get("related_plates", [])
            if related_plates:
                for rel in related_plates:
                    plate_name = rel.get("plate_name", "")
                    if plate_name.startswith("ST"):
                        continue
                    plate_count[plate_name] = plate_count.get(plate_name, 0) + 1

        # 删除"其他"并存储为单独变量
        other_plate = plate_count.pop("其他", 0)  # 若存在，取出其他板块计数，否则为0

        # 按照数量降序排序
        sorted_plates = sorted(plate_count.items(), key=lambda x: -x[1])  # 降序排列

        # 生成索引文件
        with open("板块索引.txt", "w", encoding="gbk") as f:
            for index, (plate_name, count) in enumerate(sorted_plates, start=1):
                f.write(f"{index}\t{plate_name}\n")  # 用制表符分隔
            # 将“其他”放到最后
            if other_plate > 0:
                f.write(f"x\t其他\n")  # "其他"以 x 作为前缀
                logger.info(f"添加板块： {other_plate}    其他")

    def extract_reasons(self, data):
        reasons = {}
        if data.get("code") == 20000 and data.get("message") == "OK":  # 检查返回状态
            for item in data.get("data", []):
                surge_reason = item.get("surge_reason", {})

                # 增加检查，确保 surge_reason 是一个字典，并不是 None
                if not isinstance(surge_reason, dict):
                    logger.warning(f"无效的冲击理由: {surge_reason}，在股票: {item.get('stock_chi_name')}")
                    continue

                related_plates = surge_reason.get("related_plates", [])

                # 获取符号并拆分
                symbol_get = surge_reason.get("symbol", "")
                symbol_split = symbol_get.split(".")

                if len(symbol_split) == 2:  # 确保分割后的格式正确
                    symbol_prefix = symbol_split[0]
                    symbol_suffix = symbol_split[1]
                    # 替换前缀为 SS 的 symbol
                    if symbol_suffix == "SS":
                        symbol_suffix = "SH"
                    symbol = symbol_suffix + symbol_prefix  # 股票代码

                    # 合并理由到字典中
                    if symbol not in reasons:
                        reasons[symbol] = []  # 使用列表来存储板块

                    for rel in related_plates:
                        plate_name = rel.get("plate_name", "")
                        reasons[symbol].append(plate_name)  # 添加板块

                else:
                    logger.warning(f"符号格式不正确: {symbol_get}，在股票: {item.get('stock_chi_name')}")
        return reasons

    def generate_plate_reasons(self):
        all_reasons = {}
        first_occurrence_days = {}  # 记录每个股票第一次出现的板块和日期
        last_days = {}  # 记录每个股票最新出现的板块及日期

        # 获取今天的数据
        today_data = self.get_today_data()
        today_reasons = self.extract_reasons(today_data)
        all_reasons.update(today_reasons)

        # 获取近五个工作日的数据
        valid_dates = []  # 有效的历史日期
        date = datetime.datetime.now() - datetime.timedelta(days=1)  # 从昨天开始获取历史数据

        while len(valid_dates) < 5:
            date_str = date.strftime("%Y-%m-%d")
            try:
                history_data = self.get_history_data(date_str)
                history_reasons = self.extract_reasons(history_data)

                # 更新同个股票对应的板块及日期
                for symbol, plates in history_reasons.items():
                    # 如果股票在今天的涨停理由中，则跳过
                    if symbol in today_reasons:
                        continue

                    if symbol not in last_days:
                        last_days[symbol] = {}
                    if symbol not in first_occurrence_days:
                        first_occurrence_days[symbol] = {}

                    for plate in plates:
                        # 记录首次出现的日期
                        if plate not in first_occurrence_days[symbol]:
                            first_occurrence_days[symbol][plate] = date_str
                        last_days[symbol][plate] = date_str  # 记录这个板块的出现日期

                    # 合并历史理由到 all_reasons
                    if symbol not in all_reasons:
                        all_reasons[symbol] = []
                    all_reasons[symbol].extend(plates)  # 添加历史板块

                valid_dates.append(date_str)  # 存入有效日期
            except Exception as e:
                logger.info(f"未能获取数据: {date_str} - {str(e)}")  # 跳过失败的调用
            date -= datetime.timedelta(days=1)  # 日期往前推一天

        # 在此处进行数据累积完成后的写入操作
        self.write_reasons_to_file(all_reasons, today_reasons, first_occurrence_days)

    def write_reasons_to_file(self, all_reasons, today_reasons, first_occurrence_days):
        """将累积的理由写入文件"""
        with open("涨停理由.txt", "w", encoding="gbk") as f:
            for symbol, plates in all_reasons.items():
                plates_with_days = []  # 保持唯一性，避免重复
                today_plates = today_reasons.get(symbol, [])

                # 添加今日出现的板块
                plates_with_days.extend(today_plates)

                # 检查历史记录
                for plate in plates:
                    if plate not in today_plates:  # 只检查历史记录
                        if plate in first_occurrence_days.get(symbol, {}):
                            first_date = first_occurrence_days[symbol][plate]
                            # 计算到第一次出现的天数
                            days_ago = (datetime.datetime.now() - datetime.datetime.strptime(first_date,
                                                                                             "%Y-%m-%d")).days
                            plates_with_days.append(f"{plate}(+{days_ago + 1})")  # +1是因为今天算作第一天

                # 将唯一板块用制表符连接并写入文件
                f.write(f"{symbol}\t{' '.join(list(set(plates_with_days)))}\n")

        logger.info("涨停理由文件生成成功")

    def generate_limit_up_numbers(self):
        # 校验板块索引文件和涨停理由文件是否存在
        if not (
                self.check_file_exists("板块索引.txt")
                and self.check_file_exists("涨停理由.txt")
        ):
            logger.error("请确保 '板块索引.txt' 和 '涨停理由.txt' 文件均已生成。")
            return

        # 加载板块索引
        plate_index = {}
        try:
            with open("板块索引.txt", "r", encoding="gbk") as f:
                for line_number, line in enumerate(f, start=1):
                    logger.debug(f"读取行 {line_number}: {line.strip()}")
                    parts = line.strip().split("\t")
                    if parts[0] == "x":
                        plate_name = " ".join(parts[1:])
                        plate_index[plate_name.strip()] = 55
                        continue
                    if len(parts) >= 2:
                        index = int(parts[0])
                        plate_name = " ".join(parts[1:])
                        plate_index[plate_name.strip()] = index
                    else:
                        logger.warning(f"行格式不正确: 行号 {line_number} 内容 {line.strip()}")

            logger.info(f"板块索引加载成功: {plate_index}")
        except Exception as e:
            logger.error(f"加载板块索引文件时出错: {str(e)}")
            return

        # 读取涨停理由数据并生成涨停数字
        stock_numbers = {}
        try:
            with open("涨停理由.txt", "r", encoding="gbk") as f:
                for line in f:
                    parts = line.strip().split()
                    symbol = parts[0]
                    plates = parts[1:]
                    indices = []

                    # 逐个板块寻找对应的索引
                    for plate in plates:
                        index = plate_index.get(plate.strip(), None)  # 检查板块索引
                        if index is not None:
                            indices.append(index)
                        else:
                            indices.append(0)  # 没有找到索引时用0占位

                    # 将股票符号和索引存储到字典中，确保符号保持一致
                    stock_numbers[symbol] = indices

            with open("涨停数字.txt", "w", encoding="gbk") as f:  # 统一编码
                for symbol, counts in stock_numbers.items():
                    # 排序并根据数量格式化输出
                    counts_sorted = sorted(counts)

                    # 根据数量处理格式
                    if len(counts_sorted) == 1:
                        formatted_number = str(counts_sorted[0])  # 只有一个数字
                    elif len(counts_sorted) == 2:
                        formatted_number = f"{counts_sorted[0]}.{counts_sorted[1]}"  # 两个数字
                    elif len(counts_sorted) >= 3:
                        # 取最小的三个数字并排序
                        smallest_three = sorted(counts_sorted)[:3]
                        if smallest_three[0] == 0:
                            formatted_number = "0"  # 如果最小的数字是0，直接返回0
                        else:
                            formatted_number = f"{smallest_three[0]}.{''.join(map(str, smallest_three[1:]))}"  # 格式化为 x.yz

                    f.write(f"{symbol}\t{formatted_number}\n")  # 使用制表符作为分隔

            logger.info("涨停数字文件生成成功")

        except FileNotFoundError:
            logger.error("涨停理由文件未找到，请先生成。")
            return

    def check_file_exists(self, filename):
        """检查文件是否存在"""
        try:
            with open(filename, "r"):
                return True
        except FileNotFoundError:
            return False


if __name__ == "__main__":
    g = gupiao()
    g.generate_plate_index()  # 调用生成板块索引的函数
    g.generate_plate_reasons()  # 调用生成涨停理由的函数
    g.generate_limit_up_numbers()  # 调用生成涨停数字的函数
